package com.slightech.marvin.api.doc.parser;

import com.slightech.marvin.api.doc.annotation.MarvinApiGenericField;
import com.slightech.marvin.api.doc.annotation.MarvinApiParamField;
import com.slightech.marvin.api.doc.model.ApiField;
import com.slightech.marvin.api.doc.util.StringTool;
import lombok.val;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author willardwang
 * @description
 * @date 2019/08/02
 */
public class FieldParser implements IFieldParser {

    @Override
    public FieldCollection parseClazzField(Class clz) {
        val apiFieldList = new ArrayList<ApiField>();
        val fieldMap = this.parseClazzField(clz, apiFieldList, 0, null);
        val fieldCollection = new FieldCollection();
        fieldCollection.setApiFieldList(apiFieldList);
        fieldCollection.setApiFieldMap(fieldMap);
        return fieldCollection;
    }

    @Override
    public FieldCollection parseClazzField(Class clz, MarvinApiGenericField[] marvinApiGenericFields) {
        val apiFieldList = new ArrayList<ApiField>();
        val fieldMap = this.parseClazzField(clz, apiFieldList, 0, marvinApiGenericFields);
        val fieldCollection = new FieldCollection();
        fieldCollection.setApiFieldList(apiFieldList);
        fieldCollection.setApiFieldMap(fieldMap);
        return fieldCollection;
    }

    @Override
    public FieldCollection parseMarvinApiParamField(MarvinApiParamField[] marvinApiParamFields) {
        val apiFieldList = new ArrayList<ApiField>();
        val fieldMap = new HashMap<String, Object>();
        for (MarvinApiParamField field : marvinApiParamFields) {
            val apiRequestParam = new ApiField();
            apiRequestParam.setType(field.type());
            apiRequestParam.setDescription(field.description());
            apiRequestParam.setExample(field.example());
            apiRequestParam.setRequired(field.required());
            apiRequestParam.setFieldName(field.name());
            apiFieldList.add(apiRequestParam);
            if (!field.type().equals("file")) {
                if (FieldMapper.isBasicArray(field.type())) {
                    DocField docField = FieldMapper.getDocField(field.type());
                    apiRequestParam.setType(docField.getShowType());
                    if (!StringUtils.isEmpty(field.example())) {
                        fieldMap.put(field.name(), getListFieldExampleValue(field.type(), field));
                    } else {
                        fieldMap.put(field.name(), docField.getExampleValue());
                    }
                } else {
                    fieldMap.put(field.name(), this.getFieldExampleValue(field.type(), field));
                }
            }
        }
        val fieldCollection = new FieldCollection();
        fieldCollection.setApiFieldList(apiFieldList);
        fieldCollection.setApiFieldMap(fieldMap);
        return fieldCollection;
    }

    private Map<String, Object> parseClazzField(Class clz, List<ApiField> apiFieldList, int level,
                                                MarvinApiGenericField[] marvinApiGenericFields) {
        val classFieldMap = new HashMap<String, Object>();
        Field[] fields = clz.getDeclaredFields();
        for (Field field : fields) {
            this.field2ApiFieldParam(field, apiFieldList, level, classFieldMap, marvinApiGenericFields);
        }
        return classFieldMap;
    }

    private void field2ApiFieldParam(Field field, List<ApiField> apiFieldList, int level, Map<String, Object> classFieldMap,
                                     MarvinApiGenericField[] marvinApiGenericFields) {
        boolean isStatic = Modifier.isStatic(field.getModifiers());
        if (isStatic) {
            return;
        }
        String fieldName = StringTool.humpToLine2(field.getName());
        val apiFieldParam = new ApiField();
        MarvinApiParamField marvinApiParamField = field.getAnnotation(MarvinApiParamField.class);
        apiFieldParam.setRequired(true);
        if (marvinApiParamField != null) {
            apiFieldParam.setDescription(marvinApiParamField.description());
            apiFieldParam.setExample(marvinApiParamField.example());
            apiFieldParam.setRequired(marvinApiParamField.required());
            if (!StringUtils.isEmpty(marvinApiParamField.name())) {
                fieldName = marvinApiParamField.name();
            }
        }
        String tableFieldName = this.getLevelTab(level) + fieldName;
        apiFieldParam.setFieldName(tableFieldName);
        if (FieldMapper.isBasicType(field.getGenericType().getTypeName())) {
            DocField docField = FieldMapper.getDocField(field.getGenericType().getTypeName());
            apiFieldParam.setType(docField.getShowType());
            apiFieldList.add(apiFieldParam);
            classFieldMap.put(fieldName, this.getFieldExampleValue(field.getGenericType().getTypeName(), marvinApiParamField));
            return;
        }

        if (field.getType() == List.class) {
            Type type = field.getGenericType();
            if (type instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                Type[] actualTypes = parameterizedType.getActualTypeArguments();
                Type typeHead = actualTypes[0];
                if (FieldMapper.isBasicType(typeHead.getTypeName())) {
                    DocField docField = FieldMapper.getDocField(field.getGenericType().getTypeName());
                    apiFieldParam.setType(docField.getShowType());
                    apiFieldList.add(apiFieldParam);
                    if (marvinApiParamField != null && !StringUtils.isEmpty(marvinApiParamField.example())) {
                        classFieldMap.put(fieldName, getListFieldExampleValue(typeHead.getTypeName(), marvinApiParamField));
                    } else {
                        classFieldMap.put(fieldName, docField.getExampleValue());
                    }
                } else {
                    apiFieldParam.setType("json array object");
                    apiFieldList.add(apiFieldParam);
                    Class clazz;
                    if (!(actualTypes[0] instanceof Class)) {
                        clazz = this.findGenericFieldClass(fieldName, marvinApiGenericFields);
                        if (clazz == null) {
                            apiFieldParam.setType("json array object unkonw T ");
                        }
                    } else {
                        clazz = (Class) actualTypes[0];
                    }
                    if (clazz == null) {
                        return;
                    }
                    level++;
                    val clazzFiledMap = this.parseClazzField(clazz, apiFieldList, level, marvinApiGenericFields);
                    List subList = new ArrayList();
                    subList.add(clazzFiledMap);
                    subList.add(clazzFiledMap);
                    classFieldMap.put(fieldName, subList);
                    level--;
                }
            }
        } else {
            apiFieldParam.setType("json object");
            apiFieldList.add(apiFieldParam);
            level++;
            val clazzFiledMap = this.parseClazzField(field.getType(), apiFieldList, level, marvinApiGenericFields);
            classFieldMap.put(fieldName, clazzFiledMap);
            level--;
        }
    }

    private Object getFieldExampleValue(String typeName, MarvinApiParamField marvinApiParamField) {
        Object fieldExample;
        if (marvinApiParamField != null && !StringUtils.isEmpty(marvinApiParamField.example())) {
            fieldExample = FieldMapper.getObjectValue(typeName, marvinApiParamField.example());
            return fieldExample;
        } else {
            DocField docField = FieldMapper.getDocField(typeName);
            if (docField != null) {
                fieldExample = docField.getExampleValue();
                return fieldExample;
            }
        }
        return "unkonw";
    }

    private Object getListFieldExampleValue(String typeName, MarvinApiParamField marvinApiParamField) {
        String example = marvinApiParamField.example();
        String[] exampleList = example.split(",");
        List<Object> valueList = new ArrayList<>();
        for (String e : exampleList) {
            valueList.add(FieldMapper.getObjectValue(typeName, e));
        }
        return valueList;
    }

    private String getLevelTab(int level) {
        if (level == 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < level; i++) {
            sb.append("&nbsp;&nbsp;&nbsp;&nbsp;");
        }
        sb.append("|");
        for (int i = 0; i < level; i++) {
            sb.append("__");
        }
        return sb.toString();
    }

    private Class findGenericFieldClass(String fieldName, MarvinApiGenericField[] marvinApiGenericFields) {
        for (MarvinApiGenericField marvinApiGenericField : marvinApiGenericFields) {
            if (marvinApiGenericField.fieldName().equals(fieldName)) {
                return marvinApiGenericField.fieldClass();
            }
        }
        return null;
    }
}
